package com.warthur.vertx.demo;

import com.warthur.vertx.demo.common.enums.Bean;
import com.warthur.vertx.demo.common.enums.HttpStatus;
import com.warthur.vertx.demo.common.enums.RouteConfig;
import com.warthur.vertx.demo.config.Config;
import com.warthur.vertx.demo.handler.CustomSockJSHandler;
import com.warthur.vertx.demo.handler.ExceptionHandler;
import com.warthur.vertx.demo.handler.SecurityHandler;
import com.warthur.vertx.demo.handler.WebLogHandler;
import io.vertx.config.ConfigRetriever;
import io.vertx.config.ConfigRetrieverOptions;
import io.vertx.config.ConfigStoreOptions;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Context;
import io.vertx.core.Promise;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
import io.vertx.core.logging.SLF4JLogDelegateFactory;
import io.vertx.ext.asyncsql.AsyncSQLClient;
import io.vertx.ext.asyncsql.MySQLClient;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.ResponseContentTypeHandler;
import io.vertx.ext.web.handler.ResponseTimeHandler;
import io.vertx.ext.web.handler.TimeoutHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandler;
import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions;
import io.vertx.redis.RedisClient;
import io.vertx.servicediscovery.ServiceDiscovery;
import io.vertx.servicediscovery.ServiceDiscoveryOptions;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;

/**
 * @author warthur
 * @date 2019/04/09
 */
@Slf4j
public class VertxApplication extends AbstractVerticle {

    private ConfigRetriever retriever;
    private AsyncSQLClient sqlClient;
    private RedisClient redisClient;

    @Override
    public void init(Vertx vertx, Context context) {
        super.init(vertx, context);

        ConfigRetrieverOptions retrieverOptions = new ConfigRetrieverOptions()
                .setScanPeriod(5000)
                .addStore(new ConfigStoreOptions()
                        .setType("file")
                        .setFormat("yaml")
                        .setConfig(new JsonObject().put("path", "vertx-config.yml")));

        this.retriever = ConfigRetriever.create(super.vertx, retrieverOptions);
    }

    @Override
    public void start(Promise<Void> future) {

        Promise<JsonObject> promise = Promise.promise();
        retriever.getConfig(promise);

        promise.future().setHandler(as -> {
            if (as.succeeded()) {
                log.info("获取配置信息成功");

                this.sqlClient = MySQLClient.createShared(vertx, as.result().getJsonObject("mysql"));
                this.redisClient = RedisClient.create(vertx, as.result().getJsonObject("redis"));

                // 写入配置到vertx上下文里
                vertx.getOrCreateContext().put(Bean.REDIS_CLIENT.getName(), this.redisClient);
                vertx.getOrCreateContext().put(Bean.MYSQL_CLIENT.getName(), this.sqlClient);

                createServer(future, as.result().getJsonObject("vertx"));
            } else {
                log.info("启动报错：读取配置文件失败", as.cause());
            }
        });

    }

    @Override
    public void stop(Promise<Void> stopFuture) throws Exception {
        super.stop(stopFuture);
        this.sqlClient.close(as -> {
            if (as.succeeded()) {
                log.info("异步sql客户端已关闭");
            } else {
                log.error("异步sql客户端关闭失败：", as.cause());
            }
        });
    }

    private void createServer(Promise<Void> future, JsonObject yml) {
        Config config = new Config(yml);
        HttpServer server = vertx.createHttpServer(config.getHttpServerOptions());

        Router router = createRouter();

        server.requestHandler(router).listen((asyncResult) -> {
            if (asyncResult.succeeded()) {
                future.complete();
                log.info("启动 VertxApplication，端口：{}", asyncResult.result().actualPort());
            } else {
                future.fail(asyncResult.cause());
                log.info("启动失败：{}", asyncResult.cause().getMessage());
            }
        });
    }

    private Router createRouter() {
        Router router = Router.router(vertx);
        Router http = Router.router(vertx);

        router.route()
                .handler(ResponseContentTypeHandler.create())
                .produces("application/json;charset=utf-8");

        // websocket路由
        SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000);
        SockJSHandler sockJsHandler = SockJSHandler.create(vertx, options);
        sockJsHandler.socketHandler(CustomSockJSHandler::handle);

        router.route("/ws").handler(sockJsHandler);
        router.route("/eventbus/*").handler(sockJsHandler);

        Arrays.stream(HttpStatus.values()).forEach(httpStatus ->
                router.errorHandler(httpStatus.value(), ExceptionHandler::exception));

        // http路由
        http.route()
                .handler(BodyHandler.create())
                .handler(ResponseTimeHandler.create())
                .handler(WebLogHandler::handle)
                .failureHandler(ExceptionHandler::exception);

        Arrays.stream(RouteConfig.values()).forEach(routeConfig -> {
            Route route = http.route(routeConfig.getUri()).method(routeConfig.getHttpMethod());
            if (routeConfig.isAuth()) {
                route.handler(SecurityHandler::authorize);
            }
            route.handler(routeConfig.getHandler())
                    .handler(TimeoutHandler.create(30000));
        });

        router.mountSubRouter("/api", http);
        return router;
    }


    public static void main(String[] args) {
        System.setProperty("vertx.logger-delegate-factory-class-name", SLF4JLogDelegateFactory.class.getName());

        Vertx vertx = Vertx.vertx();
        ServiceDiscovery discovery = ServiceDiscovery.create(vertx,
                new ServiceDiscoveryOptions()
                        .setAnnounceAddress("service-announce")
                        .setName("my-name"));

        vertx.deployVerticle(VertxApplication.class.getName());
    }
}
